#include "scanner.h"
#include "stl-util.h"
#include <cctype>
#include <cassert>


using tinyscript::internal::IsValidSymbolInitialChar;
using tinyscript::internal::IsValidSymbolRestChar;

namespace tinyscript{
namespace internal{
#define MODT(O,T) \
    do { \
    t->index_start_ = index; \
    t->index_end_ = index+O; \
    t->token_ = T; \
    if(pos) \
        *pos = index+O; }while(0)

bool Scanner::ScanOperators( Token* t , size_t* pos , char_t c0 , size_t index ) {

#define _DO(S,T) \
case S: MODT(1,T); return true
	switch(c0) {
		_DO('+',Token::TT_ADD);
		_DO('-',Token::TT_SUB);
		_DO('*',Token::TT_MUL);
		_DO('/',Token::TT_DIV);
		_DO('%',Token::TT_MOD);
		_DO('#',Token::TT_POUND);
		_DO('$',Token::TT_DOLLAR);
		_DO('@',Token::TT_AT);
		_DO('{',Token::TT_LLBRACKET);
		_DO('}',Token::TT_RLBRACKET);
		_DO('[',Token::TT_LMBRACKET);
		_DO(']',Token::TT_RMBRACKET);
		_DO('(',Token::TT_LSBRACKET);
		_DO(')',Token::TT_RSBRACKET);
		_DO(';',Token::TT_END);
		_DO(',',Token::TT_COMMA);
		_DO('.',Token::TT_DOT);
		_DO('!',Token::TT_NOT);
		_DO(0,Token::TT_EOF);

#undef _DO
	case '=':
		if( buffer_.CharAt(index+1) == '=' ) {
			MODT(2,Token::TT_EQ);
			return true;
		} else {
			MODT(1,Token::TT_ASSIGN);
			return true;
		}
	case '~':
		if( buffer_.CharAt(index+1) == '=' ) {
			MODT(2,Token::TT_NEQ);
			return true;
		} else {
			return false;
		}
	case '>':
		if( buffer_.CharAt(index+1) == '=' ) {
			MODT(2,Token::TT_GET);
			return true;
		} else {
			MODT(1,Token::TT_GT);
			return true;
		}
	case '<':
		if( buffer_.CharAt(index+1) == '=' ) {
			MODT(2,Token::TT_LET);
			return true;
		} else {
			MODT(1,Token::TT_LT);
			return true;
		}
	case '&':
		if( buffer_.CharAt(index+1) == '&' ) {
			MODT(2,Token::TT_NOT);
			return true;
		} else {
			MODT(1,Token::TT_AMP);
			return true;
		}
	case '|':
		if( buffer_.CharAt(index+1) == '|' ) {
			MODT(2,Token::TT_OR);
			return true;
		} else {
			ReportError(std::string("Unrecognized character:"),c0);
			return false;
		}
	default:
		ReportError(std::string("Unrecognized character:"),c0);
		return false;
	}
}

// Keyword matching
// Keyword : function , if , else , elseif ,do , while , for , pass
// break , continue
bool Scanner::ScanSymbolOrKeyword(Token *t, size_t *pos, char_t c0 , size_t index ) {
    // For this routine it should be able to scan all the global variable , local variable ,
    // keyword and constants
    switch(c0) {
	case 'b':
		if( buffer_.CharAt(index+1) == 'r' &&
			buffer_.CharAt(index+2) == 'e' &&
			buffer_.CharAt(index+3) == 'a' &&
			buffer_.CharAt(index+4) == 'k' &&
			!IsValidSymbolRestChar(buffer_.CharAt(index+5)) ) {
				MODT(5,Token::TT_BREAK);
				return true;
		}
		break;
	case 'c':
		if( buffer_.CharAt(index+1) == 'o' &&
			buffer_.CharAt(index+2) == 'n' &&
			buffer_.CharAt(index+3) == 't' &&
			buffer_.CharAt(index+4) == 'i' &&
			buffer_.CharAt(index+5) == 'n' &&
			buffer_.CharAt(index+6) == 'u' &&
			buffer_.CharAt(index+7) == 'e' &&
			!IsValidSymbolRestChar(buffer_.CharAt(index+8))) {
				MODT(8,Token::TT_CONTINUE);
				return true;
		}
		break;
	case 'd':
		if( buffer_.CharAt(index+1) == 'o' &&
			!IsValidSymbolRestChar(buffer_.CharAt(index))) {
				MODT(2,Token::TT_DO);
				return true;
		}
		break;
    case 'e' :
        if( buffer_.CharAt(index+1) == 'l' &&
            buffer_.CharAt(index+2) == 's' &&
            buffer_.CharAt(index+3) == 'e' ) {
            // else / elseif
            if( buffer_.CharAt(index+4) =='i' &&
                buffer_.CharAt(index+5) == 'f') {
                MODT(6,Token::TT_ELSEIF);
                return true;
            } else {
                MODT(4,Token::TT_ELSE);
                return true;
            }
        }
		break;
	case 'f':
		if( buffer_.CharAt(index+1) == 'o' &&
			buffer_.CharAt(index+2) == 'r' ) {
				if( buffer_.CharAt(index+3) != 'e'
					&& !IsValidSymbolRestChar(buffer_.CharAt(index+3))) {
						MODT(3,Token::TT_FOR);
						return true;
				} else if( buffer_.CharAt(index+3) == 'e' &&
					buffer_.CharAt(index+4) == 'a' &&
					buffer_.CharAt(index+5) == 'c' &&
					buffer_.CharAt(index+6) == 'h' &&
					!IsValidSymbolRestChar(buffer_.CharAt(index+7)) ) {
						MODT(7,Token::TT_FOREACH);
						return true;
				}
		} else if(buffer_.CharAt(index+1) == 'u' &&
			buffer_.CharAt(index+2) == 'n' &&
			buffer_.CharAt(index+3) == 'c' &&
			buffer_.CharAt(index+4) == 't' &&
			buffer_.CharAt(index+5) == 'i' &&
			buffer_.CharAt(index+6) == 'o' &&
			buffer_.CharAt(index+7) == 'n' &&
			!IsValidSymbolRestChar(buffer_.CharAt(index+8))) {
				MODT(8,Token::TT_FUNCTION);
				return true;
		} else if( buffer_.CharAt(index+1) == 'a' &&
			buffer_.CharAt(index+2) == 'l' &&
			buffer_.CharAt(index+3) == 's' &&
			buffer_.CharAt(index+4) == 'e' &&
			!IsValidSymbolRestChar(buffer_.CharAt(index+5)) ) {
				MODT(5,Token::TT_FALSE);
				return true;
		}
		break;
	case 'g':
		if( buffer_.CharAt(index+1) == 'l' &&
			buffer_.CharAt(index+2) == 'o' &&
			buffer_.CharAt(index+3) == 'b' &&
			buffer_.CharAt(index+4) == 'a' &&
			buffer_.CharAt(index+5) == 'l' &&
			!IsValidSymbolRestChar(buffer_.CharAt(index+6))) {
				MODT(6,Token::TT_GLOBAL);
				return true;
		}
		break;
    case 'i':
        if( buffer_.CharAt(index+1) == 'f' &&
            !IsValidSymbolRestChar(buffer_.CharAt(index+2)) ) {
            MODT(2,Token::TT_IF);
            return true;
		}
		break;
	case 'n':
		if( buffer_.CharAt(index+1) == 'u' &&
			buffer_.CharAt(index+2) == 'l' &&
			buffer_.CharAt(index+3) == 'l' &&
			!IsValidSymbolRestChar(buffer_.CharAt(index+4))) {
				MODT(4,Token::TT_NULL);
				return true;
		}
		break;
    case 'p':
        if( buffer_.CharAt(index+1) == 'a' &&
                buffer_.CharAt(index+2) == 's' &&
                buffer_.CharAt(index+3) == 's' &&
                !IsValidSymbolRestChar(buffer_.CharAt(index+4)) ) {
            MODT(4,Token::TT_PASS);
            return true;
        }
		break;
	case 'r':
		if( buffer_.CharAt(index+1) == 'e' &&
			buffer_.CharAt(index+2) == 't' &&
			buffer_.CharAt(index+3) == 'u' &&
			buffer_.CharAt(index+4) == 'r' &&
			buffer_.CharAt(index+5) == 'n' &&
			!IsValidSymbolRestChar(buffer_.CharAt(index+6)) ) {
				MODT(6,Token::TT_RETURN);
				return true;
		}
		break;
	case 't':
		if( buffer_.CharAt(index+1) == 'r' &&
			buffer_.CharAt(index+2) == 'u' &&
			buffer_.CharAt(index+3) == 'e' &&
			!IsValidSymbolRestChar(buffer_.CharAt(index+4)) ) {
				MODT(4,Token::TT_TRUE);
				return true;
		}
		break;
	case 'v':
		if( buffer_.CharAt(index+1) == 'a' &&
			buffer_.CharAt(index+2) == 'r' &&
			!IsValidSymbolInitialChar(buffer_.CharAt(index+3)) ) {
				MODT(3,Token::TT_VAR);
				return true;
		}
		break;
	case 'w':
		if( buffer_.CharAt(index+1) == 'h' &&
			buffer_.CharAt(index+2) == 'i' &&
			buffer_.CharAt(index+3) == 'l' &&
			buffer_.CharAt(index+4) == 'e' &&
			!IsValidSymbolRestChar(buffer_.CharAt(index+5)) ) {
				MODT(5,Token::TT_WHILE);
				return true;
		}
		break;
    case '\"':
        // String literals
        return GetString(t,pos,c0,index);
	// digits goes here
	case '0':case '1':case '2':case '3':case '4':
	case '5':case '6':case '7':case '8':case '9':
		return GetNumber(t,pos,c0,index);
    default:
		if( IsValidSymbolInitialChar(c0) )
			return GetSymbol(t,pos,c0,index);
		else {
			ReportError("Unrecognized character:",c0);
			return false;
		}
    }
    return GetSymbol(t,pos,c0,index);
}

bool Scanner::GetSymbol(Token *t, size_t *pos, char_t c0, size_t index) {
    assert( IsValidSymbolInitialChar(c0) );
    char_t c;
    t->index_start_ = index;
    do {
        ++index;
        c = buffer_.CharAt(index);
    } while( IsValidSymbolRestChar(c) );
    t->index_end_ = index;
    t->token_ = Token::TT_SYMBOL;
    *pos =index;
    return true;
}

bool Scanner::GetString(Token *t, size_t *pos, char_t c0, size_t index) {
    assert( c0 == '\"' );
    char_t c;
    t->index_start_ = index+1;
    do {
        ++index;
        c= buffer_.CharAt(index);
        // The string may have escape characters , the scanner will not bother
        // handling this but the scanner should be able to skip this esacpe
        // character to find out where the strings ends.
        if( c == '\\' ) {
            ++index;
        }
    } while( c != '\"' );
    t->index_end_ = index;
    *pos = index+1;
    t->token_ = Token::TT_STRING;
    return true;
}

bool Scanner::GetNumber(Token *t, size_t *pos, char_t c0, size_t index) {
    assert( isdigit(c0) );
    char_t c;
    t->index_start_ = index;
    // The number here can be Integer and float , here we need to parse
    // the exact right version of the number
    enum {
        EXPECT_DOT,
        EXPECT_FLOAT_PART,
        EXPECT_END
    };
    int state = EXPECT_DOT;
    t->token_ = Token::TT_INT;
    do {
        ++index;
        c = buffer_.CharAt(index);
        switch(state) {
        case EXPECT_DOT:
            if( c == '.' ) {
                state = EXPECT_FLOAT_PART;
                t->token_  = Token::TT_FLOAT;
            } else if( !isdigit(c) )
                goto end;
            break;
        case EXPECT_FLOAT_PART:
            if( isdigit(c) )
                state = EXPECT_END;
			else {
				ReportError("Float,expecting digits:",c0);
                // Must be a syntax error here
                return false;
			}
            break;
        case EXPECT_END:
            if( !isdigit(c) )
                goto end;
            break;
        default:
            assert(0);
        }
    } while( true );

end:
    t->index_end_ = index;
    *pos = index;
    return true;
}
// Although it is used to scan the whitespace, however it will also check
// the function will still used to detect the line breaker as well so it
// will update the line number 
void Scanner::ScanWhitespace( Token* t , size_t* pos , char_t c0 , size_t index ) {
    assert(isspace(c0));
	if( c0 == '\n' ) {
		++current_line_;
	}
    t->index_start_ = index;
    do {
        ++index;
		if( buffer_.CharAt(index) == '\n' ) {
			++current_line_;
		}
    }while( isspace(buffer_.CharAt(index)) );
    t->token_ = Token::TT_WHITESPACE;
    t->index_end_ = index;
    *pos = index;
}

namespace {
class CommentSkipper {
public:
    CommentSkipper( const internal::StringBufferHelper& buf ) :
        buffer_(buf){}

    bool CommentStart( size_t index ) const {
        return buffer_.CharAt(index) == '|' &&
               buffer_.CharAt(index+1) == '[';
    }

    bool CommentEnd( size_t index ) const {
        return buffer_.CharAt(index) == ']' &&
               buffer_.CharAt(index+1) == '|';
    }

    bool SkipComment( size_t index , size_t* pos ) {
        assert(CommentStart(index));
        // Skip the comments here
        ++index;
        do {
            ++index;
        } while( buffer_.CharAt(index) != 0 &&
               !CommentEnd(index) );
        // Skip the comments end
        *pos = index+2;
        // Status return, if EOF meets, which means that the buffer_.CharAt(index) == 0
        // so we have encountered something that cannot happened. Error !
        if( buffer_.CharAt(index) ==0 )
            return false;
        else
            return true;
    }

private:
    const internal::StringBufferHelper& buffer_;
};
}
// This is the main scanning routine. The Step function will first try to
// scan all the whitespace. And if the function is called with MODE_IGNORE_WHITESPACE,
// a recursive call to step will be invoke to return the next token after whitespace.
// If no space found, the function will try to skip the comments and this is the
// default behavior. No comments will pass to the parser. And if no comments found,
// the function will try to scan operators and keyword or symbol. If nothing found,
// an error will return and report the error with position.
Token Scanner::Step( size_t* pos , char_t c0 , size_t index , int m ) {
    Token ret;
    CommentSkipper comments(buffer_);
    // Ensure that the ScanOperator and ScanVariableOrKeyword will not handling any
    // whitespace and also the comments. Lex the whitespace and skip the comments
    // at the very beginning of each step operation .
    if( isspace(c0) ) {
        ScanWhitespace(&ret,pos,c0,index);
        if( m == MODE_IGNORE_WHITESPACE ) {
			return Step(pos,buffer_.CharAt(ret.index_end()),ret.index_end(),MODE_NORMAL);
		}
		return ret;
    } else if( comments.CommentStart(index) ) {
		if( !comments.SkipComment(index,pos) ) {
			// Comments will not report error, the Scanner should report it
			ReportError("Comments is not closing!");
            return Token();
		}
        c0 = buffer_.CharAt(*pos);
        return Step(pos,c0,*pos,m);
		// Find the operators
    } else if( !ScanOperators(&ret,pos,c0,index) ) {
		// Find the symbol or keyword
        if( ScanSymbolOrKeyword(&ret,pos,c0,index) )
            return ret;
        return Token();
    } else {
        return ret;
    }
}

// Step the scanner to fetch the next possible token . This is the most useful
Token Scanner::Advance( int m ) {
    // Advance the scanners
    AdvanceChar();
    return Step(&current_index_,c0_,current_index_-1,m);
}

// The peek function is used to peek ahead of the token , it will not change the
// scanner, but gives you hint of the next token inside of the stream.
Token Scanner::Peek( int m ) {
    char_t c;
    size_t ci;
    PeekChar(&c,&ci);
    return Step(&ci,c,ci-1,m);
}

Token Scanner::Peek( const Token& t , int m ) {
	char_t c;
	PeekChar(t.index_end(),&c);
	size_t ci;
	return Step(&ci,c,t.index_end(),m);
}

void Scanner::ReportError( const std::string& error , char_t cha /* = 0 */ ) {
	error_description_.clear();
	error_description_.resize(1024);
	int size;
	if( cha ) {
		size = sprintf(tinyscript::util::StringAsArray(&error_description_),"Scanner:(%d)%s,%c",current_line_,error.c_str(),cha);
	} else {
		size = sprintf(tinyscript::util::StringAsArray(&error_description_),"Scanner:(%d)%s.",current_line_,error.c_str());
	}
	assert( size < 1024 );
	error_description_.resize(size);
}

}// namespace internal
}// namespace sscript
